use std::convert::TryInto;
use std::str::FromStr;

use crate::helpers::{count_ttr, link_finder};
use crate::keyboards;
use crate::post::Post;
use crate::post::PostBuilder;
use crate::user::{User, UserBuilder, UserPost};
use anyhow::Context;
use anyhow::Result;
use futures::StreamExt;
use mongodb::bson::{doc, from_document, to_document};

use mongodb::bson::oid::ObjectId;
use mongodb::options::FindOneOptions;
use mongodb::Database;
use serde::{Deserialize, Serialize};
use teloxide::prelude2::*;
pub async fn add(bot: AutoSend<Bot>, m: Message) -> Result<()> {
    println!("{:#?}", &m.kind);
    let urls = link_finder(&m);
    if urls.is_empty() {
        println!("No urls!");
    }
    println!("{:#?}", urls);
    for url in urls {
        let db = connect_to_db().await?;
        let real_url = real_url(&url).await;
        let telegram_user = m.from().context("NoneError")?;
        let user_id = telegram_user.id;
        let user_username = telegram_user.username.clone();
        let user_id = {
            if let Ok(user_id) = user_exists(user_id, &db).await {
                user_id
            } else {
                create_user(user_id, user_username, &db).await?
            }
        };
        if let Ok(post_id) = post_exists(&real_url, &db).await {
            if check_duplicates_in_post_map(user_id, post_id, &db, &url)
                .await
                .is_err()
            {
                append_post_to_user(user_id, post_id, &db, &url).await?;
            }
            bot.send_message(telegram_user.id, url)
                .reply_markup(keyboards::standart_keyboard(
                    &post_id.to_hyphenated().to_string(),
                ))
                .await?;
        } else {
            let post = create_post(real_url).await?;
            save_post_to_db(&post, &db).await?;
            let post_id = post.id();
            if check_duplicates_in_post_map(user_id, post_id, &db, &url)
                .await
                .is_err()
            {
                append_post_to_user(user_id, post_id, &db, &url).await?;
            }
            log::info!("Successful saved post");
            let mut answer = {
                /*if let Some(title) = post.clone().title {
                    format!("[{}]({})", title, url)
                }
                else {*/
                url
                // }
            };
            let ttr = count_ttr(post.word_count);
            if let Some(ttr) = ttr {
                answer.push_str(&format!(" ({} minutes)", ttr))
            };

            bot.send_message(telegram_user.id, answer)
                .reply_markup(keyboards::standart_keyboard(
                    &post.clone().id().to_hyphenated().to_string(),
                ))
                //            .parse_mode(teloxide::types::ParseMode::MarkdownV2)
                .await?;
        }
    }

    Ok(())
}
use url::Url;
async fn real_url(original_url: &str) -> String {
    match reqwest::get(original_url).await {
        Err(e) => {
            println!("{:#?}", e);
            original_url.into()
        }
        Ok(res) => {
            println!("{:#?}", &res);
            //TODO: work with url
            let mut real_url = {
                let mut host: String = String::new();
                match res.url().host() {
                    Some(host_path) => host = host_path.to_string(),
                    None => {
                        println!("No host!");
                    }
                }
                let path = res.url().path().to_string();
                if let Some(query) = res.url().query() {
                    host + &path + "?" + query
                } else {
                    host + &path
                }
            };
            println!("{}", real_url);
            if Url::parse(&real_url).is_err() {
                real_url = original_url.into();
            }
            real_url
        }
    }
}
use chrono::{DateTime, Utc};
//use mongodb::bson::DateTime;
use uuid::Uuid;
fn created() -> DateTime<Utc> {
    Utc::now()
}
use crate::db::connect_to_db;
use mongodb::bson::Document;

async fn post_exists(post_url: &str, db: &Database) -> Result<Uuid> {
    let coll = db.collection::<Post>("posts");
    println!("test adding");
    let match_aggr = doc! {
    "$match":{
        "real_url": post_url
    }
    };
    let proj_aggr = doc! {
    "$project":{
    "id":1_i32,
    "_id":0_i32,
    }
    };
    let pipeline = [match_aggr, proj_aggr];
    let vec_res = coll
        .aggregate(pipeline, None)
        .await?
        .next()
        .await
        .context("NoneError")??
        .get_binary_generic("id")?
        .to_owned()
        .as_slice()
        .try_into()
        .context("error while trying to convert slice to uuid")?;
    println!("{:#?}", vec_res);
    let res = Uuid::from_bytes(vec_res);
    Ok(res)
}
use mongodb::bson::{spec::BinarySubtype, Binary};
async fn check_duplicates_in_post_map(
    user_id: Uuid,
    post_id: Uuid,
    db: &Database,
    _original_url: &str,
) -> Result<()> {
    let coll = db.collection::<Document>("userposts");
    let bin_id = Binary {
        subtype: BinarySubtype::Uuid,
        bytes: post_id.as_bytes().to_vec(),
    };
    let match1_aggr = doc! {
    "$match": {
        "user_id": user_id,
        "post_id": bin_id,
    }
    };
    let proj_aggr = doc! {
        "$project":{
            "id": 1_i32,
            "_id":0_i32
        }
    };
    let pipeline = [match1_aggr, proj_aggr];
    let _res = coll
        .aggregate(pipeline, None)
        .await?
        .next()
        .await
        .context("No duplicates")??;
    Ok(())
}
async fn append_post_to_user(
    user_id: Uuid,
    post_id: Uuid,
    db: &Database,
    original_url: &str,
) -> Result<()> {
    let coll = db.collection::<UserPost>("userposts");
    let post_builder = UserPost::builder()
        .id(Uuid::new_v4())
        .user_id(user_id)
        .original_url(original_url.to_string())
        .favorite(false)
        .post_id(post_id)
        .read(false)
        .show_real_url(false)
        .read_at(None)
        .build();
    coll.insert_one(post_builder, None).await?;
    Ok(())
}
async fn save_post_to_db(post: &PostBuilder, db: &Database) -> Result<()> {
    let coll = db.collection::<PostBuilder>("posts");
    //let document = to_document(post)?;
    let res = coll.insert_one(post, None).await?;
    println!("{:#?}", res);
    Ok(())
}
async fn get_post_id(real_url: &str, db: &Database) -> Result<Uuid> {
    let coll = db.collection::<Document>("posts");
    let filter = doc! {
        "real_url": real_url
    };
    let projection = doc! {
        "id": 1
    };
    let options = FindOneOptions::builder().projection(projection).build();
    let doc = coll.find_one(filter, options).await?.context("NoneError")?;
    let res = doc.get_binary_generic("id")?.clone();

    /*.get("id")
    .context("NoneError")?.
    .context("NoneError")?;*/
    let res = Uuid::from_str(&String::from_utf8(res)?)?;
    Ok(res)
}
pub async fn create_post(real_url: String) -> Result<PostBuilder> {
    let created = created();
    let id = uuid::Uuid::new_v4();
    //let MercuryReturn { title, word_count } = parse(&real_url).context("NoneError")?;
    let crate::helpers::MercuryOutput {
        title, word_count, ..
    } = crate::helpers::parse(&real_url)?;
    let post = PostBuilder::builder()
        .id(id)
        .real_url(real_url)
        .created(created)
        .title(title)
        .word_count(word_count)
        .build();
    Ok(post)
}
/*pub async fn user_exists(user_id: i64, db: &Database) -> Result<Uuid>{
   let coll = db.collection::<User>("users");
   let match_aggr = doc!{
    "$match":{
    "telegram_id": user_id
    }
};
let proj_aggr = doc!{
    "$project": {
    "id": 1_i32,
    "_id":0_i32
    }
};
let pipeline = [match_aggr,proj_aggr];
let options = None;
let data = coll.aggregate(pipeline, options).await.context("tetet")?.next().await.context("tetete")??;
println!("{:#?}", data);
let data = data
        .get_binary_generic("id").context("262:")?
        .to_owned()
        .as_slice()
        .try_into()
        .context("error while trying to convert slice to uuid")?;
Ok(Uuid::from_bytes(data))
}*/
async fn user_exists(user_id: i64, db: &Database) -> Result<Uuid> {
    let coll = db.collection::<User>("users");
    println!("test adding");
    let match_aggr = doc! {
    "$match":{
        "telegram_id": user_id
    }
    };
    let proj_aggr = doc! {
    "$project":{
    "id":1_i32,
    //"_id":0,
    }
    };
    let pipeline = [match_aggr, proj_aggr];
    let vec_res = coll
        .aggregate(pipeline, None)
        .await?
        .next()
        .await
        .context("NoneError")??;
    println!("{:#?}", vec_res);
    let vec_res = from_document::<MongoRes>(vec_res)?;
    println!("{:#?}", vec_res);
    let res = vec_res.id;
    Ok(res)
}

use mongodb::bson::serde_helpers::uuid_as_binary;
#[derive(Debug, Deserialize, Serialize)]
struct MongoRes {
    _id: ObjectId,
    #[serde(with = "uuid_as_binary")]
    id: Uuid,
}
pub async fn create_user(
    user_id: i64,
    user_username: Option<String>,
    _db: &Database,
) -> Result<Uuid> {
    let joined = chrono::Utc::now();
    let id = uuid::Uuid::new_v4();
    let user = UserBuilder::builder()
        .id(id)
        .telegram_id(user_id)
        .telegram_username(user_username)
        .joined(joined.into())
        .show_real_url(false)
        .build();
    let db = crate::db::connect_to_db().await?;
    let coll = db.collection::<Document>("users");
    coll.insert_one(to_document(&user)?, None).await?;
    Ok(id)
}
